import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

// Handles mail and output file polling as well as sending mail
// to the user. Does security and validation stuff as well.
public class MailPoller extends Thread {
	// The time period used for polling, in milliseconds.
	private final int POLL_INTERVAL = 3000;
	// The email address to poll.
	private String pollTarget = null;
	// The password for the email address to poll.
	private String password = null;
	
	// The class responsible for managing access to the email account via
	// POP3.
	private GmailUtils pollSource = null;
	
	// The list of devices associated with this email address; these devices
	// are controllable by messages sent to this address.
	private List<Device> devices = new LinkedList<Device>();
	
	// Used to control whether the thread should poll or not. A bit of
	// a hack and could introduce race conditions or something, so this
	// needs to be cleaned up.
	// TODO: Mutex this thing!
	public boolean shouldRun = true;
	
	// TODO: Move these properties into a new class that handles polling
	// for output; ideally, maybe remove polling altogether and handle
	// this sort of stuff through return codes or something?
	// Should probably try to avoid interprocess communication and use
	// dynamic Java libraries or something....
	
	// Used to return the instigator of the command to all users so people
	// can see who did what. This is a security precaution.
	private String messageSender;
	// TODO: This will only deal with one message at a time; however, we
	// deal with the case where the email account has more than 1 new message
	// and each message is dealt with sequentially. What we need to to is
	// to create a message class that will also contain the sender so we
	// can deal with them individually in a queue.
	
	// End move.
	
	public void setPollTarget(String newPollTarget) {
		this.pollTarget = newPollTarget;		
	}
	
	public String getPollTarget() {
		return this.pollTarget;
	}
	
	public void setPollPassword(String pw) {
		this.password = pw;
	}
	
	public boolean addDevice (Device newDevice) {
		if (this.devices.indexOf(newDevice) == -1) {
			this.devices.add(newDevice);
			return true;
		}
		return false;
	}
	
	public boolean removeDevice (Device oldDevice) {
		return this.devices.remove(oldDevice);
	}

	
	private boolean verifyState() {
		// Errors
		if (this.pollTarget == null) {
			System.out.println("Error: No polling source indicated. (Call setPollSource() first)");
			return false;
		}
		
		if (this.password == null) {
			System.out.println("Error: No password indicated. (Call setPassword() first)");
			return false;
		}
		
		// Warnings
		if (this.devices.isEmpty()) {
			System.out.println("Warning: No devices added.");
		}
		
		return true;
	}
	
	@Override
	public void run() {
		// Guard statement to ensure that the poller has everything it needs
		// to function properly. Will display error and warning messages.
		if (!this.verifyState()) return;
		
		this.pollSource = new GmailUtils();
		this.pollSource.setUserPass(this.pollTarget, this.password);
        System.out.println(this.pollTarget + " " + this.password);
        
        try {
	        
			
			for (;;) {
				if (shouldRun) {
					this.pollSource.connect();
			        this.pollSource.openFolder("Inbox");
			        
			        // TODO: Split the following into two poller classes, one for email
			        // and one for output files.
					this.pollEmail();
					this.pollDeviceOutput();
					
					this.pollSource.disconnect();
					
					sleep(this.POLL_INTERVAL);
				}
			}
		} catch (Exception e) {
			// TODO: Handle these exceptions properly....
            e.printStackTrace();
            System.exit(-1);
		}
	}
	
	private synchronized void pollEmail() throws Exception {
		int totalMessages = this.pollSource.getMessageCount();
        int newMessages = this.pollSource.getNewMessageCount();
        
        System.out.println("---");
        System.out.println(totalMessages);
        System.out.println(newMessages);
        System.out.println("---");
        
        if (newMessages > 0) {
        	System.out.println("New messages detected!");
        }
        
        for (int i = 0; i < newMessages; ++i) {
        	int messageIndex = totalMessages + i;
        	String sender = this.pollSource.getMessageSender(messageIndex);
        	

        	messageSender = sender;
        	
        	String message = this.pollSource.extractFirstLine(messageIndex);
        	message.trim();
        	String tokens[] = message.split("[ \t]+");
        	if (tokens[0].compareToIgnoreCase("help") == 0) {
        		String devList = this.returnDeviceAliases();
        		this.sendAcknowledgement(messageSender, devList);
        	} else {
	        	for (int j = 0; j < this.devices.size(); ++j) {
	        		Device curDevice = this.devices.get(j);
	            	if (curDevice.isStringAlias(tokens[0])) {
	            		// Checks to see if the sender is in the device's ACL.
	                	if (!curDevice.isSenderValid(sender)) break;
	                	
	                	// If the name is not specified, then this returns a list of valid commands
	                	// for the device.
	            		if (tokens.length == 1) {
	            			Vector<String> deviceCommands = curDevice.getCommandList();
	            			String commandList = "Commands for " + tokens[0] + ":\n";
	            			for (int k = 0; k < deviceCommands.size(); ++k) {
	            				String curCommands[] = deviceCommands.get(k).split(",");
	            				commandList += curCommands[0] + "\n";
	            			}
	            			Vector<String> allowedUsers = curDevice.getAllowedUsers();
	                		if (allowedUsers == null) {
	                			this.sendAcknowledgement(messageSender, commandList);
	                		} else {
	                			this.sendAckToUsers(allowedUsers, commandList);
	                		}
	            		} else {
	            			// Otherwise, delegates the command to the device.
	            			curDevice.handleCommand(tokens);
	            		}
	            		break;
	            	}		
	        	}    	
        	}
        }
	}
	
	private String returnDeviceAliases() {
		String devList = "Devices:\n";
		Vector<String> deviceAliases;
    	for (int i = 0; i < this.devices.size(); ++i) {
    		Device curDevice = this.devices.get(i);
    		deviceAliases = curDevice.getAliases();
    		for (int j = 0; j < deviceAliases.size(); ++j) {
    			System.out.println(deviceAliases.get(j));
    			devList += deviceAliases.get(j) + "\n";
    		}
    	}
    	return devList;
	}
	
	// TODO: Move all this stuff into a new class....
	private synchronized void pollDeviceOutput() {
		//TODO: need lock
		int numDevices = this.devices.size();
		for (int i = 0; i < numDevices; ++i) {
			Device curDevice = this.devices.get(i);
			String outputFilePath = curDevice.getOutputFilePath();
			if (outputFilePath != null) {
				File f = new File(outputFilePath);
				// Checks if the specified output file exists. If it does, then
				// the device driver has returned something that we need to send
				// back to the user.
				if (f.exists()) {
					try {
						FileReader reader = new FileReader(outputFilePath);
					    BufferedReader br = new BufferedReader(reader);
					    
					    // Reads the first line of the output text file and then sends
					    // a text file to the user.
					    String s = br.readLine();
					    
					    if (s != null) {
						    if (this.messageSender != null) {
						    	s += "\nCalled by " + this.messageSender;
						    }
						    
						    Vector<String> recipients = curDevice.getAllowedUsers();
						    
					    	if (recipients != null) {
					    		this.sendAckToUsers(recipients, s);
					    	} else if (this.messageSender != null) {
					    		this.sendAcknowledgement(this.messageSender, s);
					    	}
					    }
					    
					    // To indicate that we've handled the output, we delete the
					    // output file. This makes sure we don't end up sending duplicate
					    // messages back to the user constantly.
					    br.close();
					    reader.close();
						f.delete();
					} catch (Exception e) {
						// TODO: Handle exceptions properly....
					}
				}
			}
		}	
	}
	
	private void sendAckToUsers(Vector<String> recipients, String message) {
		if (recipients != null) {
			int numUsers = recipients.size();
			for (int i = 0; i < numUsers; ++i) {
				this.sendAcknowledgement(recipients.get(i), message);
			}
		}
	}
	
	private void sendAcknowledgement(String recipient, String message) {
		String host = "smtp.gmail.com";
	    String from = Constants.username;
	    String pass = Constants.password;
	    
		SMSSender sms = new SMSSender(host, from, pass);
		sms.sendTextMessage(recipient, message);
	}

}
